# https://docs.python.org/zh-cn/3/library/unittest.html
# 测试用例:
#   一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。
#   unittest 提供一个基类： TestCase ，用于新建测试用例。
# 测试套件:
#   test suite 是一系列的测试用例，或测试套件，或两者皆有。它用于归档需要一起执行的测试。
# 测试运行器（test runner）:
#   test runner 是一个用于执行和输出测试结果的组件。
#   这个运行器可能使用图形接口、文本接口，或返回一个特定的值表示运行测试的结果。

import unittest


class TestStringMethods(unittest.TestCase):
    """这是一段简短的代码，来测试三种字符串方法:"""

    # 继承 unittest.TestCase 就创建了一个测试样例。
    # 上述三个独立的测试是三个类的方法，这些方法的命名都以 test 开头。
    # 这个命名约定告诉测试运行者类的哪些方法表示测试。TestStringMethods

    # 每个测试的关键是：
    #   调用 assertEqual() 来检查预期的输出；
    #   调用 assertTrue() 或 assertFalse() 来验证一个条件；
    #   调用 assertRaises() 来验证抛出了一个特定的异常。
    # 使用这些方法而不是 assert 语句是为了让测试运行者能聚合所有的测试结果并产生结果报告。
    def test_upper(self):
        print('test_upper')
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        print('test_isupper')
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        print('test_split')
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

    # 通过 setUp() 和 tearDown() 方法，可以设置测试开始前与完成后需要执行的指令。
    # 在 组织你的测试代码 中，对此有更为详细的描述。
    # 若 setUp() 成功运行，无论测试方法是否成功，都会运行 tearDown() 。
    def setUp(self) -> None:
        print('\nsetUp')

    def tearDown(self) -> None:
        print('teardown\n')


# 最后的代码块中，演示了运行测试的一个简单的方法。
# unittest.main() 提供了一个测试脚本的命令行接口。
# 在调用测试脚本时添加 -v 参数使 unittest.main() 显示更为详细的信息
if __name__ == '__main__':
    unittest.main()

# 命令行接口
# unittest 模块可以通过命令行运行模块(即文件相对路径)、类和独立测试方法的测试:
"""python -m unittest test_module1 test_module2"""
"""python -m unittest test_module.TestClass"""
"""python -m unittest test_module.TestClass.test_method"""
# 你可以传入模块名、类或方法名或他们的任意组合。

# 同样的，测试模块可以通过文件路径指定:
"""python -m unittest tests/test_something.py"""
# 这样就可以使用 shell 的文件名补全指定测试模块。所指定的文件仍需要可以被作为模块导入。
# 路径通过去除 '.py' 、把分隔符转换为 '.' 转换为模块名。
# 若你需要执行不能被作为模块导入的测试文件，你需要直接执行该测试文件。

# 在运行测试时，你可以通过添加 -v 参数获取更详细（更多的冗余）的信息。
"""python -m unittest -v test_module"""

# 当运行时不包含参数，开始 探索性测试
"""python -m unittest"""

# 用于获取命令行选项列表：
"""# python -m unittest -h"""

# 命令行选项
# -b, --buffer
# 在测试运行时，标准输出流与标准错误流会被放入缓冲区。
# 成功的测试的运行时输出会被丢弃；
# 测试不通过时，测试运行中的输出会正常显示，错误会被加入到测试失败信息。

# -c, --catch
# 当测试正在运行时， Control-C 会等待当前测试完成，并在完成后报告已执行的测试的结果。
# 当再次按下 Control-C 时，引发平常的 KeyboardInterrupt 异常。

# -f, --failfast
# 当出现第一个错误或者失败时，停止运行测试。

# -k
# Only run test methods and classes that match the pattern or substring.
# This option may be used multiple times,
# in which case all test cases that match any of the given patterns are included.

# 包含通配符（*）的模式使用 fnmatch.fnmatchcase() 对测试名称进行匹配。
# 另外，该匹配是大小写敏感的。

# 模式对测试加载器导入的测试方法全名进行匹配。

# 例如，-k foo 可以匹配到 foo_tests.SomeTest.test_something
# 和 bar_tests.SomeTest.test_foo ，但是不能匹配到 bar_tests.FooTest.test_something。

# --locals
# 在回溯中显示局部变量。


# Unittest支持简单的测试搜索。
"""python -m unittest discover"""
# python -m unittest 与 python -m unittest discover 等价。
# 如果你需要向探索性测试传入参数，必须显式地使用 discover 子命令。

# discover 有以下选项：

# -v, --verbose
#   更详细地输出结果。

# -s, --start-directory directory
#   开始进行搜索的目录(默认值为当前目录 . )。

# -p, --pattern pattern
#   用于匹配测试文件的模式（默认为 test*.py ）。

# -t, --top-level-directory directory
#   指定项目的最上层目录（通常为开始时所在目录）。

# -s ，-p 和 -t 选项可以按顺序作为位置参数传入。以下两条命令是等价的：
'''python -m unittest discover -s project_directory -p "*_test.py"'''
'''python -m unittest discover project_directory "*_test.py"'''

# 正如可以传入路径那样，传入一个包名作为起始目录也是可行的，如 myproject.subpackage.test。
# 你提供的包名会被导入，它在文件系统中的位置会被作为起始目录。

# 警告
#   探索性测试通过导入测试对测试进行加载。在找到所有你指定的开始目录下的所有测试文件后，
#   它把路径转换为包名并进行导入。如 foo/bar/baz.py 会被导入为 foo.bar.baz 。
#   如果你有一个全局安装的包，并尝试对这个包的副本进行探索性测试，可能会从错误的地方开始导入。
#   如果出现这种情况，测试会输出警告并退出。
#   如果你使用包名而不是路径作为开始目录，搜索时会假定它导入的是你想要的目录，所以你不会收到警告。

# 测试模块和包可以通过 load_tests protocol 自定义测试的加载和搜索。
